cmake_minimum_required(VERSION 3.21.1)

if(APPLE)
    set(ENV{MACOSX_DEPLOYMENT_TARGET} "11.0")
    set(ENV{VCPKG_KEEP_ENV_VARS} "MACOSX_DEPLOYMENT_TARGET")
endif()

option(DEPS_RELEASE_ONLY "Build only release versions of vcpkg dependencies" OFF)

if(DEPS_RELEASE_ONLY)
    if(NOT DEFINED VCPKG_TARGET_TRIPLET)
        message(FATAL_ERROR "Must provide a VCPKG_TARGET_TRIPLET to set as release only")
    endif()
    if(NOT VCPKG_TARGET_TRIPLET MATCHES "-release$")
        set(VCPKG_TARGET_TRIPLET "${VCPKG_TARGET_TRIPLET}-release")
        message("Updated VCPKG_TARGET_TRIPLET to ${VCPKG_TARGET_TRIPLET}")
    endif()
endif()

if(APPLE)
    set(MAYBE_MACOSX_BUNDLE MACOSX_BUNDLE)
    if(VCPKG_TARGET_TRIPLET MATCHES "^x64")
        set(CMAKE_OSX_ARCHITECTURES "x86_64")
        set(VCPKG_TARGET_ARCHITECTURE "x64")
    else()
        set(CMAKE_OSX_ARCHITECTURES "arm64")
        set(VCPKG_TARGET_ARCHITECTURE "arm64")
    endif()
endif()

project(CEmu
        VERSION 3.0
        LANGUAGES C CXX)

if(NOT EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/../../core/debug/zdis/zdis.c" OR
   NOT EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/tivars_lib_cpp/src/TIVarFile.cpp")
    message(FATAL_ERROR "Some files seem to be missing - you have to run 'git submodule init' and 'git submodule update' first.")
endif()

if(WIN32 AND VCPKG_TARGET_TRIPLET MATCHES "-static(-|$)" AND NOT VCPKG_TARGET_TRIPLET MATCHES "-md(-|$)")
    message("Using static MSVC runtime...")
    set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>")
endif()

set(CMAKE_INCLUDE_CURRENT_DIR ON)
set(CMAKE_SKIP_RPATH TRUE)

set(USED_CMAKE_GENERATOR "${CMAKE_GENERATOR}" CACHE STRING "Expose CMAKE_GENERATOR" FORCE)
message(STATUS "Detected system: ${CMAKE_SYSTEM_NAME} - host processor: ${CMAKE_HOST_SYSTEM_PROCESSOR} - CXX_COMPILER: ${CMAKE_CXX_COMPILER_ID}")

set(CMAKE_C_STANDARD 11)
set(CMAKE_C_STANDARD_REQUIRED ON)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

# Sane flags
if(MSVC)
    set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /utf-8 /D_CRT_SECURE_NO_WARNINGS")
    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /utf-8 /D_CRT_SECURE_NO_WARNINGS")
    if(MSVC_VERSION GREATER_EQUAL 1935 AND NOT CMAKE_CXX_COMPILER_ID MATCHES "Clang")
        set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /experimental:c11atomics")
    endif()
elseif(CMAKE_CXX_COMPILER_ID MATCHES "Clang" OR CMAKE_CXX_COMPILER_ID MATCHES "GNU")
    # sane defaults + hardening
    set(GLOBAL_COMPILE_FLAGS "-W -Wall -Wextra -Wno-unused-parameter -Werror=write-strings -Wredundant-decls -Werror=date-time -Werror=return-type -Werror=pointer-arith")
    set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${GLOBAL_COMPILE_FLAGS} -Werror=implicit-function-declaration -Werror=missing-prototypes")
    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${GLOBAL_COMPILE_FLAGS}")
    # useful flags for debugging
    set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -fno-omit-frame-pointer -fsanitize=address,bounds -fsanitize-undefined-trap-on-error ")
    set(CMAKE_EXE_LINKER_FLAGS_DEBUG "${CMAKE_EXE_LINKER_FLAGS_DEBUG} -fno-omit-frame-pointer -fsanitize=address,bounds -fsanitize-undefined-trap-on-error ")
endif()

include(GNUInstallDirs)

find_package(QT NAMES Qt6 REQUIRED COMPONENTS Core)
find_package(Qt6 REQUIRED COMPONENTS Gui Network Widgets)
if(NOT WIN32)
    find_package(Qt6 COMPONENTS DBus)
endif()

set(CMAKE_AUTORCC ON)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTOUIC ON)
if(COMMAND qt_standard_project_setup)
    qt_standard_project_setup()
endif()

qt_add_resources(CEmu_Resources resources.qrc)

set(CEmu_Sources
    ../../core/asic.c ../../core/asic.h
    ../../core/atomics.h
    ../../core/backlight.c ../../core/backlight.h
    ../../core/bootver.c ../../core/bootver.h
    ../../core/bus.c ../../core/bus.h
    ../../core/cert.c ../../core/cert.h
    ../../core/control.c ../../core/control.h
    ../../core/cpu.c ../../core/cpu.h
    ../../core/debug/debug.c ../../core/debug/debug.h
    ../../core/debug/zdis/zdis.c ../../core/debug/zdis/zdis.h
    ../../core/defines.h
    ../../core/emu.c ../../core/emu.h
    ../../core/extras.c ../../core/extras.h
    ../../core/flash.c ../../core/flash.h
    ../../core/interrupt.c ../../core/interrupt.h
    ../../core/keypad.c ../../core/keypad.h
    ../../core/lcd.c ../../core/lcd.h
    ../../core/link.c ../../core/link.h
    ../../core/mem.c ../../core/mem.h
    ../../core/misc.c ../../core/misc.h
    ../../core/os/os.h
    ../../core/panel.c ../../core/panel.h
    ../../core/port.c ../../core/port.h
    ../../core/realclock.c ../../core/realclock.h
    ../../core/registers.c ../../core/registers.h
    ../../core/schedule.c ../../core/schedule.h
    ../../core/sha256.c ../../core/sha256.h
    ../../core/spi.c ../../core/spi.h
    ../../core/timers.c ../../core/timers.h
    ../../core/uart.c ../../core/uart.h
    ../../core/usb/device.h
    ../../core/usb/disconnected.c
    ../../core/usb/dusb.c
    ../../core/usb/fotg210.h
    ../../core/usb/msd.c
    ../../core/usb/physical.c
    ../../core/usb/usb.c ../../core/usb/usb.h
    ../../core/vat.c ../../core/vat.h
    ../../tests/autotester/autotester.cpp ../../tests/autotester/autotester.h
    archive/extractor.c archive/extractor.h
    basiccodeviewerwindow.cpp basiccodeviewerwindow.h basiccodeviewerwindow.ui
    basicdebugger.cpp
    capture/animated-png.c capture/animated-png.h
    cemuopts.h
    datawidget.cpp datawidget.h
    debugger.cpp
    debugger/disasm.cpp debugger/disasm.h
    debugger/hexwidget.cpp debugger/hexwidget.h
    debugger/visualizerdisplaywidget.cpp debugger/visualizerdisplaywidget.h
    dockwidget.cpp dockwidget.h
    gotodialog.cpp gotodialog.h
    emuthread.cpp emuthread.h
    ipc.cpp ipc.h
    keyhistorywidget.cpp keyhistorywidget.h
    keypad/alphakey.h
    keypad/arrowkey.cpp keypad/arrowkey.h
    keypad/graphkey.h
    keypad/key.h
    keypad/keycode.h
    keypad/keyconfig.h
    keypad/keymap.cpp keypad/keymap.h
    keypad/keypadwidget.cpp keypad/keypadwidget.h
    keypad/numkey.h
    keypad/operkey.h
    keypad/otherkey.h
    keypad/qtkeypadbridge.cpp keypad/qtkeypadbridge.h
    keypad/rectkey.cpp keypad/rectkey.h
    keypad/secondkey.h
    lcdwidget.cpp lcdwidget.h
    main.cpp
    mainwindow.cpp mainwindow.h mainwindow.ui
    memorywidget.cpp
    romselection.cpp romselection.h romselection.ui
    searchwidget.cpp searchwidget.h searchwidget.ui
    sendinghandler.cpp sendinghandler.h
    settings.cpp
    tablewidget.cpp tablewidget.h
    tivars_lib_cpp/src/BinaryFile.cpp tivars_lib_cpp/src/BinaryFile.h
    tivars_lib_cpp/src/CommonTypes.h
    tivars_lib_cpp/src/TIModel.cpp tivars_lib_cpp/src/TIModel.h
    tivars_lib_cpp/src/TIModels.cpp tivars_lib_cpp/src/TIModels.h
    tivars_lib_cpp/src/TIVarFile.cpp tivars_lib_cpp/src/TIVarFile.h
    tivars_lib_cpp/src/TIVarType.cpp tivars_lib_cpp/src/TIVarType.h
    tivars_lib_cpp/src/TIVarTypes.cpp tivars_lib_cpp/src/TIVarTypes.h
    tivars_lib_cpp/src/TypeHandlers/DummyHandler.cpp
    tivars_lib_cpp/src/TypeHandlers/STH_DataAppVar.cpp
    tivars_lib_cpp/src/TypeHandlers/STH_ExactFraction.cpp
    tivars_lib_cpp/src/TypeHandlers/STH_ExactFractionPi.cpp
    tivars_lib_cpp/src/TypeHandlers/STH_ExactPi.cpp
    tivars_lib_cpp/src/TypeHandlers/STH_ExactRadical.cpp
    tivars_lib_cpp/src/TypeHandlers/STH_FP.cpp
    tivars_lib_cpp/src/TypeHandlers/STH_PythonAppVar.cpp
    tivars_lib_cpp/src/TypeHandlers/TH_GDB.cpp
    tivars_lib_cpp/src/TypeHandlers/TH_GenericAppVar.cpp
    tivars_lib_cpp/src/TypeHandlers/TH_GenericComplex.cpp
    tivars_lib_cpp/src/TypeHandlers/TH_GenericList.cpp
    tivars_lib_cpp/src/TypeHandlers/TH_GenericReal.cpp
    tivars_lib_cpp/src/TypeHandlers/TH_Matrix.cpp
    tivars_lib_cpp/src/TypeHandlers/TH_TempEqu.cpp
    tivars_lib_cpp/src/TypeHandlers/TH_Tokenized.cpp
    tivars_lib_cpp/src/TypeHandlers/TypeHandlers.h
    tivars_lib_cpp/src/tivarslib_utils.cpp tivars_lib_cpp/src/tivarslib_utils.h
    utils.cpp utils.h
    vartablemodel.cpp vartablemodel.h
    visualizerwidget.cpp visualizerwidget.h
    ${CEmu_Resources}
)

qt_add_executable(CEmu ${MAYBE_MACOSX_BUNDLE} ${CEmu_Sources})

# TODO better, see https://stackoverflow.com/a/21028226/378298
execute_process(
    COMMAND git describe --abbrev=7 --always
    WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
    OUTPUT_VARIABLE GIT_COMMIT_HASH
    OUTPUT_STRIP_TRAILING_WHITESPACE
)
execute_process(
    COMMAND git rev-parse --abbrev-ref HEAD
    WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
    OUTPUT_VARIABLE GIT_CURR_BRANCH
    OUTPUT_STRIP_TRAILING_WHITESPACE
)

option(IS_OFFICIAL_RELEASE_VERSION "Whether the current build is for an official release" OFF)
if(IS_OFFICIAL_RELEASE_VERSION)
    set(VERSION_SUFFIX "")
    set(CEMU_RELEASE_BOOL true)
else()
    set(VERSION_SUFFIX "dev")
    set(CEMU_RELEASE_BOOL false)
endif()

set(SHORT_VERSION "v3.0" CACHE STRING "Version of CEmu in the form of 'vX.Y'")
set(LONG_VERSION "${SHORT_VERSION}${VERSION_SUFFIX} (${GIT_CURR_BRANCH} - ${GIT_COMMIT_HASH})")

target_compile_definitions(CEmu PRIVATE
    CEMU_GIT_SHA=\"${GIT_COMMIT_HASH}\"
    CEMU_RELEASE=${CEMU_RELEASE_BOOL}
    CEMU_VERSION=\"${SHORT_VERSION}${VERSION_SUFFIX}\"
    DEBUG_SUPPORT
    MULTITHREAD
    TH_GDB_SUPPORT=1
)

if(CYGWIN OR NOT WIN32)
    include(CheckSymbolExists)
    check_symbol_exists(glob "glob.h" HAVE_GLOB)
    if(HAVE_GLOB)
        target_compile_definitions(CEmu PRIVATE "GLOB_SUPPORT")
    else()
        message(WARNING "glob function not supported, AUTOTESTER_LIBS_DIR usage will not work!")
    endif()
endif()

find_package(LibArchive QUIET)
if(LibArchive_FOUND)
    target_compile_definitions(CEmu PRIVATE "LIB_ARCHIVE_SUPPORT")
    target_link_libraries(CEmu PRIVATE LibArchive::LibArchive)
else()
    message(WARNING "No LibArchive found! CE Bundle extraction/transfer will not be available")
endif()

find_package(PkgConfig REQUIRED)
if(PkgConfig_FOUND)
    pkg_check_modules(LibUsb libusb-1.0>=1.0.16)
    if(LibUsb_FOUND)
        target_compile_definitions(CEmu PRIVATE "LIBUSB_SUPPORT")
        target_include_directories(CEmu PRIVATE ${LibUsb_INCLUDE_DIRS})
        target_link_directories(CEmu PRIVATE ${LibUsb_LIBRARY_DIRS})
        target_link_libraries(CEmu PRIVATE ${LibUsb_LIBRARIES})
    else()
        message(WARNING "No LibUsb found! Some USB things will not be available")
    endif()
endif()

if((DEFINED ENV{GITHUB_ACTION}) AND APPLE AND NOT (CMAKE_HOST_SYSTEM_PROCESSOR STREQUAL "arm64"))
    message("For now, libpng-apng is disabled on intel macs on CI... see issue #528. APNG capture will not be available")
else()
    find_package(PNG QUIET)
    if(PNG_FOUND)
        include(CheckSymbolExists)
        set(CMAKE_REQUIRED_INCLUDES ${PNG_INCLUDE_DIRS})
        check_symbol_exists(PNG_WRITE_APNG_SUPPORTED "png.h" HAVE_PROPER_APNG_LIB)
        if(HAVE_PROPER_APNG_LIB)
            list(APPEND CMAKE_AUTOMOC_MOC_OPTIONS "-DPNG_SUPPORT" "-DPNG_WRITE_APNG_SUPPORTED")
            target_compile_definitions(CEmu PRIVATE "PNG_SUPPORT")
            target_link_libraries(CEmu PRIVATE PNG::PNG)
        else()
            message(WARNING "The LibPNG found does not seem to support APNG. APNG capture will not be available")
        endif()
    else()
        message(WARNING "No LibPNG found! APNG capture will not be available")
    endif()
endif()

target_link_libraries(CEmu PRIVATE
    Qt::Core
    Qt::Gui
    Qt::Network
    Qt::Widgets
)
if(TARGET Qt::DBus)
    target_link_libraries(CEmu PRIVATE Qt::DBus)
endif()

include(CheckIPOSupported)
check_ipo_supported(RESULT lto_supported OUTPUT error)
if(lto_supported)
    set_target_properties(CEmu PROPERTIES
        INTERPROCEDURAL_OPTIMIZATION_DEBUG FALSE
        INTERPROCEDURAL_OPTIMIZATION_RELEASE TRUE
        INTERPROCEDURAL_OPTIMIZATION_RELWITHDEBINFO TRUE
    )
else()
    message(STATUS "IPO/LTO not supported: <${error}>")
endif()

target_compile_definitions(CEmu PRIVATE $<$<CONFIG:Release,RelWithDebInfo>:QT_NO_DEBUG_OUTPUT>)

if(UNIX OR APPLE)
    target_sources(CEmu PUBLIC ../../core/os/os-linux.c)
endif()

if(APPLE)
    set(MACOSX_BUNDLE_ICON_FILE icon.icns)
    set(app_icon_macos "${CMAKE_CURRENT_SOURCE_DIR}/resources/icons/icon.icns")
    set_source_files_properties(${app_icon_macos} PROPERTIES MACOSX_PACKAGE_LOCATION "Resources")
    target_sources(CEmu PUBLIC
        os/mac/kdmactouchbar.h os/mac/kdmactouchbar.mm
        os/mac/kdmactouchbar_global.h
        ${app_icon_macos}
    )
    target_link_libraries(CEmu PRIVATE "-framework Cocoa")
    set_target_properties(CEmu PROPERTIES
        MACOSX_FRAMEWORK_IDENTIFIER "com.adriweb.CEmu"
        MACOSX_BUNDLE_COPYRIGHT "CE-Programming team"
        MACOSX_BUNDLE_LONG_VERSION_STRING "${LONG_VERSION}"
    )
endif()

if(WIN32)
    target_sources(CEmu PUBLIC
        ../../core/os/os-win32.c
        win32-console.cpp
        resources/windows/cemu.rc
    )
    target_link_libraries(CEmu PRIVATE psapi)
    if(CMAKE_CXX_COMPILER_ID MATCHES "Clang")
        target_link_libraries(CEmu PRIVATE clang_rt.builtins-x86_64)
    endif()
endif()

install(TARGETS CEmu
    BUNDLE DESTINATION .
    RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
)

message("Binary dir: ${CMAKE_CURRENT_BINARY_DIR}")

# TODO: linux .desktop/.xml files

if(COMMAND qt_generate_deploy_app_script)
    qt_generate_deploy_app_script(
        TARGET CEmu
        FILENAME_VARIABLE deploy_script
        NO_UNSUPPORTED_PLATFORM_ERROR
    )
    install(SCRIPT ${deploy_script})
endif()
